//
// PepSession
//
// Copyright (C) 2007 Brian Stevens.
//
//  This file is part of the JavaPepper Library.
//
//  The JavaPepper Library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2.1 of the License, or (at your option) any later version.
//
//  The JavaPepper Library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with the JavaPepper Library; if not, write to the Free
//  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
//  02111-1307 USA.
//

package net.jextra.javapepper;

import java.lang.ref.*;
import java.util.*;
import java.util.regex.*;
import javax.servlet.http.*;

/**
 * <p>
 * Represents the overall interpretation of the javapepper-config.xml file. There is a one-to-one relationship between
 * the PepSession and a web server's user session ({@link HttpSession}).
 * </p>
 * <p>
 * Another way of thinking of the PepSession is that it represent a single web users "environment".
 * </p>
 */
public class PepSession
{
    // ============================================================
    // Fields
    // ============================================================

    public static final String ERROR_RESPONDER_NOT_FOUND = "Responder for URL not found: ";

    // This is a weak reference so that the HttpSession can be garbage collected whenever the
    // web server wants to remove it. This object is invalid after this occurs.
    private WeakReference<HttpSession> httpSessionRef;

    // The resolver is the work-horse class that interprets the URL and calls the appropriate
    // code.
    private PepResolver pepResolver = new PepResolverImpl();

    private String welcomeURL = null;

    // The error responder is the responder that is called for any exceptions that propogate
    // from a doXXX.
    //    private Responder errorResponder = new PepErrorResponder();
    private int lastErrorCode;
    private Throwable lastException;

    // This is used by the resolver to lookup the registered regular expression to a specific
    // responder class or responder instance.
    private ArrayList<PepResponderRegistration> responderRegistrations;

    // This is a list of mappings from regular expression to a path to a file somewhere under the
    // webapp hierarchy.
    private ArrayList<Mapping> mappingRegistrations;

    // ============================================================
    // Constructors
    // ============================================================

    public PepSession()
    {
        responderRegistrations = new ArrayList<PepResponderRegistration>();
        mappingRegistrations = new ArrayList<Mapping>();

        //
        // Add the special resource responder.
        //
        //        responderRegistrations.add( new PepResponderRegistration( ResourceResponder.PREFIX + ".*",
        //            ResourceResponder.class.getCanonicalName() ) );
    }

    // ============================================================
    // Methods
    // ============================================================

    // ----------
    // public
    // ----------

    /**
     * A clean way to invalidate the user session.
     */
    public void invalidate()
    {
        getHttpSession().invalidate();
    }

    public void setHttpSession( HttpSession session )
    {
        httpSessionRef = new WeakReference<HttpSession>( session );
    }

    public HttpSession getHttpSession()
    {
        return httpSessionRef.get();
    }

    public void setPepResolverClass( Class<? extends PepResolver> pepResolverClass )
        throws InstantiationException, IllegalAccessException
    {
        pepResolver = (PepResolver) pepResolverClass.newInstance();
    }

    public void setErrorResponder( PepResponder errorResponder )
    {
        //        this.errorResponder = errorResponder;
    }

    public PepResponder getErrorResponder()
    {
        return null;
    }

    public Throwable getLastException()
    {
        return lastException;
    }

    public void setLastException( Throwable ex )
    {
        lastException = ex;
    }

    public int getLastErrorCode()
    {
        return lastErrorCode;
    }

    public void setLastErrorCode( int errorCode )
    {
        lastErrorCode = errorCode;
    }

    @SuppressWarnings( "unchecked" )
    public PepResponder createResponder( String url )
    {
        PepResponderRegistration item = matchResponder( url );
        PepResponder responder = null;
        // Default error code is 500
        setLastErrorCode( HttpServletResponse.SC_INTERNAL_SERVER_ERROR );

        // No registered responder to the given url already.
        if ( item == null )
        {
            setLastErrorCode( HttpServletResponse.SC_NOT_FOUND );
            throw new RuntimeException( ERROR_RESPONDER_NOT_FOUND + url );
        }
        // This is a specific instance of a responder that was added by code.
        else if ( item.getResponder() != null )
        {
            return item.getResponder();
        }
        // Create new instance of the responder.
        else
        {
            try
            {
                Class<? extends PepResolver> responderClass = (Class<? extends PepResolver>) Class.forName( item
                    .getClassName() );
                responder = (PepResponder) responderClass.newInstance();
            }
            catch ( Throwable ex )
            {
                throw new RuntimeException( ex );
            }
        }

        return responder;
    }

    /**
     * Inserts responder to front of the list of responders. It is important that it is at the front because it means
     * that it can override others that are below this one.
     */
    public boolean insertResponder( String urlRegex, String className )
    {
        // If already in list, do not add it again.
        if ( findResponder( urlRegex ) != null )
        {
            return false;
        }

        PepResponderRegistration item = new PepResponderRegistration( urlRegex, className );
        responderRegistrations.add( 0, item );
        return true;
    }

    public boolean insertResponder( String urlRegex, PepResponder responder )
    {
        PepResponderRegistration item = new PepResponderRegistration( urlRegex, responder );
        responderRegistrations.add( 0, item );
        return true;
    }

    public boolean appendResponder( String urlRegex, String className )
    {
        // If already in list, do not add it again.
        if ( findResponder( urlRegex ) != null )
        {
            return false;
        }

        PepResponderRegistration item = new PepResponderRegistration( urlRegex, className );
        responderRegistrations.add( item );
        return true;
    }

    public boolean prependResponder( String urlRegex, PepResponder responder )
    {
        // If it's already in the list, don't add it again.
        if ( findResponder( urlRegex ) != null )
        {
            return false;
        }

        PepResponderRegistration item = new PepResponderRegistration( urlRegex, responder );
        responderRegistrations.add( 0, item );
        return true;
    }

    public boolean appendResponder( String urlRegex, PepResponder responder )
    {
        // If it's already in the list, don't add it again.
        if ( findResponder( urlRegex ) != null )
        {
            return false;
        }

        PepResponderRegistration item = new PepResponderRegistration( urlRegex, responder );
        responderRegistrations.add( item );
        return true;
    }

    public boolean removeResponder( String urlRegex )
    {
        for ( int i = 0; i < responderRegistrations.size(); i++ )
        {
            if ( responderRegistrations.get( i ).getURLRegex().equalsIgnoreCase( urlRegex ) )
            {
                responderRegistrations.remove( i );
                return true;
            }
        }

        return false;
    }

    public boolean appendMapping( String urlRegex, String path )
    {
        // If it's already in the list, don't add it again.
        if ( findMapping( urlRegex ) != null )
        {
            return false;
        }

        Mapping mapping = new Mapping( urlRegex, path );
        mappingRegistrations.add( mapping );
        return true;
    }

    public String getWelcomeURL()
    {
        return welcomeURL;
    }

    public void setWelcomeURL( String welcomeURL )
    {
        this.welcomeURL = welcomeURL;
    }

    public void processRequest( HttpServletRequest request, HttpServletResponse response )
        throws Exception
    {
        pepResolver.processRequest( this, request, response );
    }

    public void forward( PepResponder responder, PepSession pepSession, PepMethod method, HttpServletRequest request,
        HttpServletResponse response )
        throws Exception
    {
        pepResolver.forward( responder, this, method, request, response );
    }

    // ----------
    // protected
    // ----------

    protected PepResponderRegistration findResponder( String urlRegex )
    {
        for ( PepResponderRegistration item : responderRegistrations )
        {
            if ( item.getURLRegex().equalsIgnoreCase( urlRegex ) )
            {
                return item;
            }
        }

        return null;
    }

    protected PepResponderRegistration matchResponder( String url )
    {
        for ( PepResponderRegistration item : responderRegistrations )
        {
            Pattern pattern = Pattern.compile( item.getURLRegex(), Pattern.CASE_INSENSITIVE );

            Matcher matcher = pattern.matcher( url );
            if ( matcher.matches() )
            {
                return item;
            }
        }

        return null;
    }

    protected Mapping findMapping( String urlRegex )
    {
        for ( Mapping item : mappingRegistrations )
        {
            if ( item.getURLRegex().equalsIgnoreCase( urlRegex ) )
            {
                return item;
            }
        }

        return null;
    }

    protected Mapping matchMapping( String url )
    {
        for ( Mapping item : mappingRegistrations )
        {
            Pattern pattern = Pattern.compile( item.getURLRegex(), Pattern.CASE_INSENSITIVE );

            Matcher matcher = pattern.matcher( url );
            if ( matcher.matches() )
            {
                return item;
            }
        }

        return null;
    }

    // ============================================================
    // Inner Classes
    // ============================================================

    protected static class PepResponderRegistration
    {
        private String _urlRegex;

        // Either the class is known or a specific instance (responder) can be registered.
        private String _className;
        private PepResponder _responder;

        public PepResponderRegistration( String urlRegex, String className )
        {
            _urlRegex = urlRegex;
            _className = className;
            _responder = null;
        }

        public PepResponderRegistration( String urlRegex, PepResponder responder )
        {
            _urlRegex = urlRegex;
            _className = null;
            _responder = responder;
        }

        public String getURLRegex()
        {
            return _urlRegex;
        }

        public void setURLRegex( String urlRegex )
        {
            _urlRegex = urlRegex;
        }

        public String getClassName()
        {
            return _className;
        }

        public void setClassName( String className )
        {
            _className = className;
            _responder = null;
        }

        public PepResponder getResponder()
        {
            return _responder;
        }

        public void setResponder( PepResponder responder )
        {
            _responder = responder;
            _className = null;
        }
    }

    protected static class Mapping
    {
        private String _urlRegex;
        private String _path;

        public Mapping()
        {
        }

        public Mapping( String urlRegex, String path )
        {
            _urlRegex = urlRegex;
            _path = path;
        }

        public String getURLRegex()
        {
            return _urlRegex;
        }

        public void setURLRegex( String urlRegex )
        {
            _urlRegex = urlRegex;
        }

        public String getPath()
        {
            return _path;
        }

        public void setPath( String path )
        {
            _path = path;
        }
    }
}
